# -*- coding: utf-8 -*-

# manpage/argparse_help.py
# Part of ‘manpage’, a Python library for making Unix manual documents.
#
# Copyright © 2016 Ben Finney <ben+python@benfinney.id.au>
#
# This is free software: see the grant of license at end of this file.

""" ArgumentParser help integration to build Unix “man page” documents. """

import argparse
import textwrap

from . import document


class CommandManPageMaker(document.ManPageMaker):
    """ Maker for a command manual page document.

        Data attributes:

        * `metadata`: A `document.MetaData` instance specifying the
          document metadata for the manual page document.

        * `parser`: The `argparse.ArgumentParser` instance for the
          command to be documented.

        * `seealso`: A collection of `document.Reference` instances.
          If not ``None``, this is used to populate the “SEE ALSO”
          section of the document.

        """

    document_class = document.CommandDocument

    def __init__(self, metadata):
        super().__init__(metadata)

        self.parser = None

    def set_parser(self, parser):
        """ Set the parser to use for generating help.

            :param parser: The instance of `argparse.ArgumentParser`
                from which to derive help text for the command.
            :return: None.

            """
        parser.formatter_class = ManPageHelpFormatter
        parser.prog = self.metadata.name
        self.parser = parser

    def make_manpage(self):
        """ Make a manual page document from the known metadata. """
        manpage = super().make_manpage()

        manpage.content_sections.update({
                "OPTIONS": self.make_options_section(),
                })

        return manpage

    def make_synopsis_section(self, text=None):
        """ Make the “SYNOPSIS” section of the document. """
        if text is None:
            text = " ".join(
                    line.strip()
                    for line in self.parser.format_usage().splitlines())

        section = super().make_synopsis_section(text)

        return section

    def make_description_section(self, text=None):
        """ Make the “DESCRIPTION” section of the document. """
        if text is None:
            text = self.parser.description

        section = super().make_description_section(text)

        return section

    def make_options_section(self):
        """ Make the “OPTIONS” section of the document. """
        section = document.DocumentSection("OPTIONS")

        options_markup = self.parser.formatter_class.format_options(
                self.parser)
        section.body = options_markup

        return section

    def make_distribution_section(self, distribution):
        """ Make the “DISTRIBUTION” section of the document. """
        section = document.DocumentSection("DISTRIBUTION")
        name_markup = document.GroffMarkup.escapetext(
                self.metadata.name, hyphen=document.GroffMarkup.glyph.minus)
        distribution_markup = document.GroffMarkup.escapetext(
                distribution.get_name())
        homepage = distribution.get_url()
        homepage_markup = document.GroffMarkup.escapetext(homepage)

        section.body = textwrap.dedent("""\
                The command
                {macro.bold} {name}
                is part of the Python distribution
                {glyph.dquote_left}{dist}{glyph.dquote_right}.
                The home page for {dist} is at
                {macro.url_begin} {homepage}
                {macro.url_end} .
                """).format(
                    macro=document.GroffMarkup.macro,
                    glyph=document.GroffMarkup.glyph,
                    name=name_markup,
                    dist=distribution_markup, homepage=homepage_markup)

        return section


class ManPageHelpFormatter(argparse.RawTextHelpFormatter):
    """ ArgumentParser help formatter to generate a Unix manual page. """

    def __init__(
            self, prog,
            indent_increment=2,
            max_help_position=8,
            width=float("inf"),
            ):
        prog_markup = self._format_literal_text(prog)
        super().__init__(
                prog_markup, indent_increment, max_help_position, width)

    def _indent(self):
        pass

    def _dedent(self):
        pass

    def _split_lines(self, text, width):
        lines = [text]
        return lines

    @staticmethod
    def _format_literal_text(text):
        """ Convert `text` to Groff markup for literal command text. """
        text_markup = document.GroffMarkup.escapetext(
                    text, hyphen=document.GroffMarkup.glyph.minus)
        result = "{font.bold}{text}{font.previous}".format(
                font=document.GroffMarkup.font, text=text_markup)
        return result

    @staticmethod
    def _format_variable_text(text):
        """ Convert `text` to Groff markup for variable parameter text. """
        text_markup = document.GroffMarkup.escapetext(
                    text, hyphen=document.GroffMarkup.glyph.minus)
        result = "{font.italic}{text}{font.previous}".format(
                font=document.GroffMarkup.font, text=text_markup)
        return result

    @staticmethod
    def _format_option_text(text):
        """ Convert `text` to Groff markup for a command option. """
        result = document.GroffMarkup.escapetext(
                text, hyphen=document.GroffMarkup.glyph.minus)
        return result

    @staticmethod
    def format_options(parser):
        """ Format the detailed options help.

            :param parser: The `ArgumentParser` instance to document.
            :return: The text of the detailed options help.

            """
        formatter = parser._get_formatter()

        for action_group in parser._action_groups:
            formatter.start_section(action_group.title)
            formatter.add_arguments(action_group._group_actions)
            formatter.end_section()

        formatter.add_text(parser.epilog)

        return formatter.format_help()

    def start_section(self, heading):
        """ Start a new subsection of the detailed actions help.

            :param heading: Text of the section heading.
            :return: None.

            """
        heading_markup = textwrap.dedent("""\
                {control.empty}
                {macro.subsection} {title}""").format(
                    control=document.GroffMarkup.control,
                    macro=document.GroffMarkup.macro,
                    title=heading.title())
        section = self._Section(self, self._current_section, heading_markup)
        self._add_item(section.format_help, [])
        self._current_section = section

    def _format_usage(self, usage, actions, groups, prefix):
        """ Make text for the usage message.

            :param usage: Text to use for the message. If not
                specified, construct the message from the remaining
                arguments.
            :param actions: Collection of `Action` instances to
                document.
            :param groups: Collection of `_ArgumentGroup` instances
                grouping the actions.
            :param prefix: Text to prefix the usage message.
            :return: Text of the usage message.

            """
        prefix = ""
        result = super()._format_usage(usage, actions, groups, prefix)
        return result

    @classmethod
    def _action_with_literal_text_markup(cls, action):
        """ Make a copy of `action`, with literal text formatted.

            :param action: The `Action` instance to document.
            :return: An `Action` instance copied from `action`, with
                literal text marked up for help output.

            """
        action_with_markup = argparse.Action(
                list(action.option_strings),
                action.dest,
                nargs=action.nargs,
                const=action.const,
                default=action.default,
                type=action.type,
                choices=action.choices,
                required=action.required,
                help=action.help,
                metavar=action.metavar)
        action_with_markup.option_strings = [
                cls._format_literal_text(item)
                for item in action.option_strings]
        if action.choices is not None:
            action_with_markup.choices = [
                    cls._format_literal_text(item)
                    for item in action.choices]

        return action_with_markup

    def _metavar_formatter(self, action, default_metavar):
        """ Provide a formatter function of the action meta-variable.

            :param action: The `Action` instance to document.
            :param default_metavar: Default “meta variable”
                (replaceable parameter) name in the arguments.
            :return: A function which takes a parameter `tuple_size`,
                a positive integer; and returns a value for formatting.

            The formatter function returns either the marked-up
            meta-variable text (if `tuple_size` is 1), or a tuple of
            `tuple_size` copies of the marked-up text.

            This method is used in the base `argparse.ArgumentParser`
            for some formatting operations.

            """
        action_with_markup = self._action_with_literal_text_markup(action)
        if action.metavar is not None:
            action_with_markup.metavar = self._format_variable_text(
                    action.metavar)

        default_metavar_with_markup = self._format_variable_text(
                default_metavar)

        result = super()._metavar_formatter(
                action_with_markup, default_metavar_with_markup)

        return result

    def _format_actions_usage(self, actions, groups):
        """ Make text documenting `actions` in the usage message.

            :param actions: Collection of `Action` instances to
                document.
            :param groups: Collection of `_ArgumentGroup` instances
                grouping the actions.
            :return: Text documenting the `actions` for the usage
                message.

            """
        marked_up_actions = [
                self._action_with_literal_text_markup(action)
                for action in actions]

        result = super()._format_actions_usage(marked_up_actions, groups)

        return result

    def _format_action_invocation(self, action):
        """ Make text detailing the invocation of `action`.

            :param action: The `Action` instance to document.
            :return: The text documenting the invocation of `action`.

            """
        default_metavar = action.dest.upper()
        action_with_markup = self._action_with_literal_text_markup(action)

        args_markup = self._format_args(action_with_markup, default_metavar)

        parts = []
        if not action_with_markup.option_strings:
            # Action is a positional parameter.
            metavar_markup = self._metavar_formatter(
                    action, default_metavar)(1)[0]
            parts.append(metavar_markup)

        else:
            # Action is a non-positional option.
            if action_with_markup.nargs == 0:
                option_template = "{option}"

            else:
                option_template = "{option} {args}"

            options_markup = ",\n".join(
                    option_template.format(
                        option=option_markup, args=args_markup)
                    for option_markup in action_with_markup.option_strings)
            parts.append(options_markup)

        result = "\n" + self._join_parts(parts)
        return result


# This is free software: you may copy, modify, and/or distribute this work
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; version 3 of that license or any later version.
#
# No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details,
# or view it online at <URL:https://www.gnu.org/licenses/gpl-3.0.html>.


# Local variables:
# coding: utf-8
# mode: python
# End:
# vim: fileencoding=utf-8 filetype=python :
